#include <arpa/inet.h>
#include <errno.h>
#include <iostream>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "count.h"
#include "generalizer.h"
#include "server.h"

void *talker (Talker &talk) {
  const char lf = '\n';
  char buffer[1024];
  FILE *f = fdopen (talk.socket, "r");
  Trail trail;
  pNode node;
  Tree *tree;
  Generalizer generalizer;
  System system (talk.server.rules, talk.server.globals, trail);
  Prover *prover;

  while(!talk.server.quit && !feof (f)) {
    std::string result = "ERROR";

    if(fgets (buffer, sizeof(buffer), f)) {
      pNode node (buffer);
      node = generalizer.generalize (*node).final ();
      if(typeid(*node.node) != typeid(Tree))
        goto response;

      tree = (Tree*) node.node;
      if(tree->ops[0] != '|' && tree->ops[1] != 0)
        goto response;

      prover = Prover::create (*tree->right, system);
      while(prover->prove ()) {
        result = tree->left.dump ();
        send (talk.socket, result.c_str (), result.length (), 0);
        send (talk.socket, &lf, sizeof(lf), 0);
      }
      delete prover;

      result = "\xFF"; // End of result
    }

response:
    send (talk.socket, result.c_str (), result.length (), 0);
    send (talk.socket, &lf, sizeof(lf), 0);
  }

  countDown (talk.server.threadCount);
  delete &talk;
  return 0;
}

void *server (SinglogServerThread *sst) {
  int server;
  socklen_t n = 0;
  struct sockaddr_in address;

  server = socket (PF_INET, SOCK_STREAM, 0);
  memset(&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(5151);
  int reuse = 1;

  setsockopt (server, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
  if(bind (server, (struct sockaddr*) &address, sizeof(address)))
    std::cerr << "Failed to bind port" << std::endl;
  listen (server, 10);

  while(!sst->quit) {
    pthread_t thread;
    int socket = accept (server, 0, &n);
    Talker *talk = new Talker (*sst, socket);
    countUp (sst->threadCount);
    if(pthread_create (&thread, NULL, (void* (*) (void*)) talker, talk)) {
      delete talk; // Something's wrong
      countDown (sst->threadCount);
    }
  }

  return 0;
}
